package p.lodz.pl.poid.zad2.fourier;

public class FastFourierTransform {
    
    private ComplexNumber[][] data;
    private Double[][] phaseTable;
    private int width, height;
    private boolean fourierTransformed = false;
    private boolean useZeroPadding = false;
    private int oriWidth;
    private int oriHeight;
    private int nW;
    private int nH;

    /**
     * Initialize a new instance of the FourierTransform class.
     * @param fastBitmap FastBitmap.
     */
    public FastFourierTransform(FastBitmap fastBitmap) {
        if (fastBitmap.isGrayscale()) {
            this.width = fastBitmap.getWidth();
            this.height = fastBitmap.getHeight();
            
            boolean a = Tools.IsPowerOf2(width);
            boolean b = Tools.IsPowerOf2(height);
            
            if (a == false || b == false){
                ZeroPadding(fastBitmap);
                this.width = fastBitmap.getWidth();
                this.height = fastBitmap.getHeight();
            }
            
            data = new ComplexNumber[height][width];
            phaseTable = new Double[height][width];

            for (int x = 0; x < height; x++) {
                for (int y = 0; y < width; y++) {
                    data[x][y] = new ComplexNumber(0, 0);
                    data[x][y].real = (float) fastBitmap.getGray(x, y) / 255;
                }
            }
        }
        else{
            try {
                throw new Exception("ComplexImage works only with Grayscale images");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    private void ZeroPadding(FastBitmap fastBitmap){
        
        this.oriWidth = fastBitmap.getWidth();
        this.oriHeight = fastBitmap.getHeight();

        this.nW = Tools.NextPowerOf2(oriWidth);
        this.nH = Tools.NextPowerOf2(oriHeight);

        ImagePadding expand = new ImagePadding(nW - oriWidth, nH - oriHeight);
        expand.applyInPlace(fastBitmap);
        this.useZeroPadding = true;
        
    }

    /**
     * Complex image width.
     * @return Width.
     */
    public int getWidth() {
        return width;
    }

    /**
     * Complex image height.
     * @return Height.
     */
    public int getHeight() {
        return height;
    }

    /**
     * Complex image's data.
     * @return Data.
     */
    public ComplexNumber[][] getData() {
        return data;
    }

    /**
     * Complex image's data.
     * @param data Data.
     */
    public void setData(ComplexNumber[][] data) {
        this.data = data;
    }

    /**
     * Status of the image - Fourier transformed or not.
     * @return <b>True</b>: is transformed, otherwise returns <b>False</b>.
     */
    public boolean isFourierTransformed() {
        return fourierTransformed;
    }
    
    /**
     * Convert Complex image's data to FastBitmap.
     * @return FastBitmap.
     */
    public FastBitmap toFastBitmap(){
        FastBitmap fb = new FastBitmap(width, height, FastBitmap.ColorSpace.Grayscale);
        
        double scale = ( fourierTransformed ) ? Math.sqrt( width * height ) : 1;
        
        for (int x = 0; x < height; x++) {
            for (int y = 0; y < width; y++) {
                fb.setGray(x, y, (int)Math.max( 0, Math.min( 255, data[x][y].getMagnitude() * scale * 255 )));
            }
        }
        
        if (useZeroPadding && !fourierTransformed){
            Crop crop = new Crop((nH - oriHeight) / 2, (nW - oriWidth) / 2, oriWidth, oriHeight);
            crop.ApplyInPlace(fb);
        }
        
        return fb;
    }
    
    /**
     * Applies forward fast Fourier transformation to the complex image.
     */
    public void Forward(){
        if (!fourierTransformed){
            for ( int x = 0; x < height; x++ ){
                for ( int y = 0; y < width; y++ ){
                    if ( ( ( x + y ) & 0x1 ) != 0 ){
                        data[x][y].real *= -1;
                        data[x][y].imaginary *= -1;
                    }
                }
            }

            FourierTransform.FFT2(data, FourierTransform.Direction.Forward);
            fourierTransformed = true;
        }
    }
    
    /**
     * Applies backward fast Fourier transformation to the complex image.
     */
    public void Backward( ){
        if ( fourierTransformed ){
            FourierTransform.FFT2(data, FourierTransform.Direction.Backward);
            fourierTransformed = false;

            for ( int x = 0; x < height; x++ ){
                for ( int y = 0; y < width; y++ ){
                    if ( ( ( x + y ) & 0x1 ) != 0 ){
                        data[x][y].real *= -1;
                        data[x][y].imaginary *= -1;
                    }
                }
            }
        }
    }
    
    public FastBitmap toMagnitudeFastBitmap() {
        FastBitmap fb = new FastBitmap(width, height, FastBitmap.ColorSpace.Grayscale);

        double scale = (fourierTransformed) ? Math.sqrt(width * height) : 1;

        for (int x = 0; x < height; x++) {
            for (int y = 0; y < width; y++) {
                fb.setGray(x, y, (int) Math.max(0, Math.min(255, data[x][y].getMagnitude() * scale * 255)));
            }
        }

        if (useZeroPadding && !fourierTransformed) {
            Crop crop = new Crop((nH - oriHeight) / 2, (nW - oriWidth) / 2, oriWidth, oriHeight);
            crop.ApplyInPlace(fb);
        }

        return fb;
    }

    public FastBitmap toPhaseFastBitmap() {
        FastBitmap fb = new FastBitmap(width, height, FastBitmap.ColorSpace.Grayscale);
        createPhaseValues();
        double scale = (fourierTransformed) ? Math.sqrt(width * height) : 1;

        for (int x = 0; x < height; x++) {
            for (int y = 0; y < width; y++) {
                fb.setGray(x, y, (int) Math.max(0, Math.min(255, data[x][y].getPhase() * scale * 255)));
            }
        }

        if (useZeroPadding && !fourierTransformed) {
            Crop crop = new Crop((nH - oriHeight) / 2, (nW - oriWidth) / 2, oriWidth, oriHeight);
            crop.ApplyInPlace(fb);
        }

        return fb;
    }

    public void createPhaseValues() {
        for (int x = 0; x < height; x++) {
            for (int y = 0; y < width; y++) {
                getPhaseTable()[x][y] = data[x][y].getPhase();
            }
        }
    }
    
    /**
     * @return the phaseTable
     */
    public Double[][] getPhaseTable() {
        return phaseTable;
    }

    /**
     * @param phaseTable the phaseTable to set
     */
    public void setPhaseTable(Double[][] phaseTable) {
        this.phaseTable = phaseTable;
    }
}